package com.tianji.learning.utils;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.JsonUtils;
import com.tianji.common.utils.StringUtils;
import com.tianji.learning.domain.po.LearningLesson;
import com.tianji.learning.domain.po.LearningRecord;
import com.tianji.learning.mapper.LearningRecordMapper;
import com.tianji.learning.service.ILearningLessonService;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.DelayQueue;

//延迟任务工具类
@Slf4j
@Component
@RequiredArgsConstructor
public class LearningRecordDelayTaskHandler {
    private final StringRedisTemplate redisTemplate;
    private final ILearningLessonService lessonService;

    //Redis中，HashMap的key前缀，避免其他业务的key冲突，覆盖数据。
    private final static String RECORD_KEY_TEMPLATE = "learning:record:{}"; //learning:record:{16562772}

    //初始化延迟队列
    private final static DelayQueue<DelayTask<RecordTaskData>> queue = new DelayQueue<>();

    //RecordService也要注入当前工具类，所以工具类注入recordMapper，防止循环依赖
    private final LearningRecordMapper recordMapper;

    //控制while循环的条件，用来@PreDestroy防止项目挂了，循环还在继续
    //volatile：让其他线程可见。防止当前线程修改了，其他线程不可见。导致其他线程还在继续执行
    private static volatile boolean begin = true;


    //当前类加载后执行。也就是项目一旦启动，该方法就去执行，监听提交的学习记录。
    @PostConstruct
    public void init() {
        //开启一个线程异步执行handleDelayTask方法
        CompletableFuture.runAsync(this::handleDelayTask);
        //CompletableFuture.runAsync(() -> this.handleDelayTask());

        //new Thread(() -> this.handleDelayTask()).start();
    }

    //整个容器销毁之前执行
    @PreDestroy
    public void destroy() {
        begin = false;
        log.debug("延迟任务停止执行！");
    }


    //处理延迟任务：检测数据是否一致
    public void handleDelayTask() {
        while (begin) {
            try {
                // 1.尝试获取到期的延迟任务
                //没有任务则阻塞。一旦有任务过期，就会执行后面的逻辑。
                DelayTask<RecordTaskData> take = queue.take();
                RecordTaskData takeData = take.getData(); //获取提交任务的数据

                //2.查询Redis缓存。根据Key和HashKey
                LearningRecord record = readRecordCache(takeData.getLessonId(), takeData.getSectionId());
                if (BeanUtils.isEmpty(record)) {
                    //未命中缓存,则跳本次循环，继续等待下个延迟任务。
                    continue;
                }

                //3.比较数据，moment值
                //延迟任务中的播放进度和20秒后redis中的播放进度进行对比
                if (!Objects.equals(takeData.getMoment(), record.getMoment())) {
                    // 不一致，说明用户还在持续提交播放进度，放弃旧数据,继续下次循环监听
                    continue;
                }

                // 4.一致，说明用户停止播放视频了，持久化播放进度数据到数据库
                //4.1.更新学习记录的moment
                record.setFinished(null);
                recordMapper.updateById(record);

                //4.2.更新课表最近学习信息
                LearningLesson lesson = new LearningLesson();
                lesson.setId(takeData.getLessonId());
                lesson.setLatestSectionId(takeData.getSectionId());
                //因为延迟任务拖了20秒，所以这里需要减少20秒，才是真正的时间。
                lesson.setLatestLearnTime(LocalDateTime.now().minusSeconds(20));
                lessonService.updateById(lesson);

            } catch (InterruptedException e) {
                log.error("处理延迟任务发生异常", e);
            }

        }
    }


    /**
     * 不是第一次学完（更新学习记录），则添加数据到缓存，
     * 并且提交延迟任务到DelayQueue，延迟时间20秒
     *
     * @param record 本次提交的学习记录
     */
    public void addLearningRecordTask(LearningRecord record) {
        //1.添加学习记录数据到Redis缓存
        writeRecordCache(record);

        //2.提交延迟任务到延迟队列 DelayQueue，20秒后去redis检测数据是否变化：
        RecordTaskData recordTaskData = new RecordTaskData(record); //延迟任务相关数据对象
        DelayTask<RecordTaskData> delayTask = new DelayTask<>(recordTaskData, Duration.ofSeconds(20)); //20延迟时间
        queue.add(delayTask);
    }


    /**
     * 查询缓存中的学习记录
     *
     * @param lessonId  课表id
     * @param sectionId 小节id
     *                  return LearningRecord 学习记录对象
     */
    public LearningRecord readRecordCache(Long lessonId, Long sectionId) {
        try {
            //1.读取Redis数据
            //将lessonId拼接到{}
            String key = StringUtils.format(RECORD_KEY_TEMPLATE, lessonId);
            Object cacheData = redisTemplate.opsForHash().get(key, sectionId.toString());
            if (BeanUtils.isEmpty(cacheData)) {
                //缓存未命中
                return null;
            }

            //2.数据检查和JSON对象转换
            return JsonUtils.toBean(cacheData.toString(), LearningRecord.class);
        } catch (Exception e) {
            log.error("缓存读取异常：", e);
            return null;
        }
    }


    /**
     * 删除缓存中的学习记录
     *
     * @param lessonId  课表id
     * @param sectionId 小节id
     */
    public void cleanRecordCache(Long lessonId, Long sectionId) {
        String key = StringUtils.format(RECORD_KEY_TEMPLATE, lessonId);
        redisTemplate.opsForHash().delete(key, sectionId.toString());
    }


    /**
     * 写学习记录到缓存中
     *
     * @param record
     */
    public void writeRecordCache(LearningRecord record) {
        log.debug("更新学习记录的缓存数据");
        try {
            //1.数据转换
            String json = JsonUtils.toJsonStr(new RecordCacheData(record));

            //2.写入Redis
            String key = StringUtils.format(RECORD_KEY_TEMPLATE, record.getLessonId()); //learning:record:{16562772}
            //redis数据结构： KEY：learning:record:{16562772}   HashKey：sectionId   HashValue：{id:1,moment:100,finished:true}
            redisTemplate.opsForHash().put(key, record.getSectionId().toString(), json);

            //3.添加缓存过期时间1分钟
            redisTemplate.expire(key, Duration.ofMinutes(1));
        } catch (Exception e) {
            log.error("更新学习记录缓存异常", e);
        }
    }


    //创建学习记录，缓存相关对象
    @Data
    @NoArgsConstructor
    private static class RecordCacheData {
        private Long id;    //学习记录id
        private Integer moment;    //播放进度
        private Boolean finished;    //小节是否已学完

        public RecordCacheData(LearningRecord record) {
            this.id = record.getId();
            this.moment = record.getMoment();
            this.finished = record.getFinished();
        }
    }


    //创建学习记录，延迟任务相关对象
    @Data
    @NoArgsConstructor
    private static class RecordTaskData {
        private Long lessonId;    //课表id
        private Long sectionId;    //小节id
        private Integer moment;    //播放进度

        //通过课表id和小节id，去缓存中找到对应小节的播放进度，再比较两个播放进度
        public RecordTaskData(LearningRecord record) {
            this.lessonId = record.getLessonId();
            this.sectionId = record.getSectionId();
            this.moment = record.getMoment();
        }
    }

}
